package com.swe727.spring2011.constraint1.client;

import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Random;

import com.swe727.spring2011.constraint1.coordinate.Coordinate;
import com.swe727.spring2011.constraint1.node.Node;
import com.swe727.spring2011.constraint1.node.NodeRegistration;
import com.swe727.spring2011.constraint1.observation.Observation;
import com.swe727.spring2011.constraint1.observation.ObservationType;
import com.swe727.spring2011.constraint1.server.*;
import com.swe727.spring2011.constraint1.client.util.*;
import java.util.*;

import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggerFactory;

public class NodeController {

	private Coordinate currentLocation;
	private final Grid GRID;
	private final int MAX_MOVES = 30;
	private final String NODE_ID;
	private final String RMI_ADDRESS;
	private final int RMI_PORT;
	private final int REST_SECONDS;

	private boolean isInstrumented;

	private final int BEGIN_MEASUREMENT = 5;
	private final int END_MEASUREMENT = 15;

	public NodeController(String nodeId, int gridSize, int restSeconds,
			String rmiRegistryAddress, int rmiRegistryPort,
			boolean measureRegisterLatency, boolean isInstrumented)
			throws RemoteException, AlreadyBoundException, NotBoundException {
		GRID = new Grid(gridSize, gridSize);
		NODE_ID = nodeId;
		RMI_ADDRESS = rmiRegistryAddress;
		RMI_PORT = rmiRegistryPort;
		REST_SECONDS = restSeconds;

		this.isInstrumented = isInstrumented;

		Registry registry = LocateRegistry.getRegistry(rmiRegistryAddress,
				rmiRegistryPort);

		// register with the zone server
		long tick = System.currentTimeMillis();
		RegisterNodeRemote registration = (RegisterNodeRemote) registry
		.lookup(ZoneServer.REGISTER_ID);
		registration.registerNode(new NodeRegistration(new Node(NODE_ID,
				rmiRegistryAddress, rmiRegistryPort)));
		
		if (measureRegisterLatency) {
			LogUtil.writeNodeRegisterLatency(NODE_ID,
					System.currentTimeMillis() - tick);
		}

		ClientMedevacHandler medevacReceiver = new ClientMedevacHandler(NODE_ID);
		ClientMedevacRemote stub = (ClientMedevacRemote) UnicastRemoteObject
				.exportObject(medevacReceiver, 0);
		registry.bind(nodeId, stub);

		// calculate random start point
		Random random = new Random(System.currentTimeMillis());
		int x = random.nextInt(gridSize);
		int y = random.nextInt(gridSize);
		currentLocation = new Coordinate(x, y);
		startSimulation();
	}

	public boolean shouldLog(int currentMove) {
		System.out
				.println("Should log = "
						+ (isInstrumented && currentMove >= BEGIN_MEASUREMENT && currentMove <= END_MEASUREMENT));
		return isInstrumented && currentMove >= BEGIN_MEASUREMENT
				&& currentMove <= END_MEASUREMENT;
	}

	private void randomSleep() {
		try {
			Thread.sleep(new Random(System.currentTimeMillis()).nextInt(2) * 1000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
	}

	private void startSimulation() throws RemoteException, NotBoundException {
		int curMove = 0;
		ObservationSimulator observationSimulator = new ObservationSimulator();

		double numObservationsRequested = 0;
		long totalGetObservationLatency = 0L;
		int medevacSentCount = 0;
		System.out.println("Beginning simulation...");

		while (curMove++ < MAX_MOVES) {
			ArrayList<Observation> myObservations = observationSimulator
					.getObservation(currentLocation);
			System.out.println("Number of observations for current location = "
					+ myObservations.size());

			Registry registry = LocateRegistry.getRegistry(RMI_ADDRESS,
					RMI_PORT);
			SaveObservationRemote saveHandler = null;
			ServerMedevacRemote medvacHandler = null;
			GetObservationRemote getObsHandler = null;

			while (saveHandler == null) {
				try {

					saveHandler = (SaveObservationRemote) registry
							.lookup(ZoneServer.SAVE_OBSERVATION_ID);
				} catch (Exception e) {
					randomSleep();
					e.printStackTrace();
				}
			}

			while (medvacHandler == null) {
				try {
					medvacHandler = (ServerMedevacRemote) registry
							.lookup(ZoneServer.MEDEVAC_ID);
				} catch (Exception e) {
					randomSleep();
					e.printStackTrace();
				}
			}
						
			while (getObsHandler == null) {
				try {
					getObsHandler = (GetObservationRemote) registry
			
					.lookup(ZoneServer.GET_OBSERVATION_ID);
				} catch (Exception e) {
					randomSleep();
					e.printStackTrace();
				}
			}
			for (Observation obs : myObservations) {
				System.out.println("Reporting observation " + obs.getData());
				saveHandler.saveObservation(obs);

				if (obs.getData() == ObservationType.MEDEVAC) {
					// set time of message to now
					currentLocation.setTimestamp(new Date());
					medevacSentCount++;
					medvacHandler.requestMedevac(currentLocation);
				}
			}

			long tick = System.currentTimeMillis();
			currentLocation = GRID.getNextMove(currentLocation);

			if (shouldLog(curMove)) {
				System.out.println("Recording number of observations...");
				numObservationsRequested++;
				totalGetObservationLatency += System.currentTimeMillis() - tick;

				// record memory usage
				LogUtil.writeMemoryUsage(NODE_ID);
			}

			int tries = 0;
			boolean keepTrying = true;
			while (keepTrying && tries++ < 5) {
				try {
					getObsHandler.getObservation(currentLocation);
					keepTrying = false;
				} catch (Exception e) {
					e.printStackTrace();
					keepTrying = true;
					randomSleep();
				}
			}

			if (shouldLog(curMove)) {
				// this should always be 100% since we are calling the server to
				// get all observations at a coordinate

			}

			try {
				LogNormalDistribution logNormalObj = new LogNormalDistribution(
						REST_SECONDS);
				long sleepTime = (long) (1000 * logNormalObj.nextLogNormal());
				System.out.println("Sleeping for " + sleepTime + " msec");
				Thread.sleep(sleepTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Beginning move " + curMove);
		}

		// everybody log number of medevacs requested and received
		LogUtil.writeRequestedMedevacCount(NODE_ID, medevacSentCount);

		Registry registry = LocateRegistry.getRegistry(RMI_ADDRESS, RMI_PORT);
		ClientMedevacRemote medevacReceiver = (ClientMedevacRemote) registry
				.lookup(NODE_ID);
		LogUtil.writeReceivedMedevacCount(NODE_ID,
				medevacReceiver.getMedevacReceivedCount());

		// calculate average latency for get observation
		if (isInstrumented) {
			LogUtil.writeLog(NODE_ID, "totalGetObsLatency="
					+ ((double) totalGetObservationLatency / 60));
			LogUtil.writeLog(NODE_ID, "getObsCount=" + numObservationsRequested);
			double averageGetLatency = ((double) totalGetObservationLatency)
					/ numObservationsRequested;
			LogUtil.writeAverageGetLatency(NODE_ID, averageGetLatency);
		}

		System.out.println("Node complete... exiting...");
		System.exit(0);
	}

	public static void main(String[] args) throws RemoteException,
			AlreadyBoundException, NotBoundException {
		// if (args.length != 5)
		// System.out.println("NodeController args[]=\n"+
		// "node name(string)\n"+
		// "number of observations prior to ending(int)\n"+
		// "seconds between observation(int)\n"+
		// "server address(string)"+
		// "server port(int)");
		// else
		// new
		// NodeController(args[0],Integer.parseInt(args[1]),Integer.parseInt(args[2]),args[3],Integer.parseInt(args[4]));

		// new NodeController("node1", 30, 6, "localhost", 5569);

		// new NodeController("node1", 30, 6, "localhost", 5569);
		if (args.length != 8) {
			System.out
					.println("NodeController <nodeId> <gridSize> <sleepTime (seconds)> <rmiServerHostname> <rmiServerPort> <measureRegisterLatency (true|false)> <isInstrumented (true|false)> <rootDir>");
		}

		LogUtil.ROOT_DIR = args[7];
		new NodeController(args[0], Integer.parseInt(args[1]),
				Integer.parseInt(args[2]), args[3], Integer.parseInt(args[4]),
				Boolean.parseBoolean(args[5]), Boolean.parseBoolean(args[6]));
	}
}
